package com.lzq.mq.broker.utils;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.concurrent.CountDownLatch;

// TODO 仅用于测试MMap
/**
 * 支持Java的MMap api访问能力（文件的读写）
 * 支持指定的offset文件映射
 * 支持从指定的offset读取
 * 支持从指定offset写入
 * 文件映射后的内存释放
 */
public class MMapUtil {
    private File file;
    private MappedByteBuffer mappedByteBuffer;
    private FileChannel fileChannel;

    /**
     *
     * @param filePath 文件路径
     * @param startOffset 开始映射的offset
     * @param mappedSize 映射的体积
     * @throws IOException
     */
    public void  loadFileInMMap(String filePath, int startOffset, int mappedSize) throws IOException {
        this.file = new File(filePath);
        if (!file.exists()) {
            throw new FileNotFoundException("文件路径(" + filePath + ")不合法");
        }
        this.fileChannel = new RandomAccessFile(file, "rw").getChannel();
        this.mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, startOffset, mappedSize);
    }

    public byte[] readContent(int readOffset, int size) {
        mappedByteBuffer.position(readOffset);
        byte[] content = new byte[size];
        for (int i=0; i<size; i++) {
            // 从内存访问, 实际上会非常快
            content[i] = mappedByteBuffer.get(readOffset + i);
        }
        return content;
    }

    /**
     * @param content 内容
     * @param force 是否强制刷盘
     */
    public void writeContent(byte[] content, boolean force) {
        mappedByteBuffer.put(content);
        if (force) {
            mappedByteBuffer.force();
        }
    }

    public void writeContent(byte[] content) {
        writeContent(content, false);
    }

    public void clean() {
        if (mappedByteBuffer == null || !mappedByteBuffer.isDirect() || mappedByteBuffer.capacity() == 0) {
            return;
        }
        // JDK 9及以后，直接调用sun.misc.Unsafe的invokeCleaner方法更稳定
        try {
            Class<?> unsafeClass = Class.forName("sun.misc.Unsafe");
            Method cleanerMethod = unsafeClass.getMethod("invokeCleaner", ByteBuffer.class);
            Object unsafe = getUnsafeInstance(unsafeClass);
            cleanerMethod.invoke(unsafe, mappedByteBuffer);
            System.out.println("通过sun.misc.Unsafe实现clean");
        } catch (Exception e) {
            // Fallback 回退到原有的反射调用方式，兼容老版本JDK
            // 获取attachment并调用cleaner的clean方法
            invoke(invoke(viewed(mappedByteBuffer), "cleaner"), "clean");
        }
    }

    /**
     * 获取Unsafe的实例（通过反射方式，避免直接依赖）
     */
    private Object getUnsafeInstance(Class<?> unsafeClass) throws Exception {
        try {
            // JDK 9+获取Unsafe实例的新方法
            Method getUnsafe = unsafeClass.getDeclaredMethod("getUnsafe");
            getUnsafe.setAccessible(true);
            return getUnsafe.invoke(null);
        } catch (Exception e) {
            // JDK 8及以下回退，使用反射获取theUnsafe字段
            java.lang.reflect.Field f = unsafeClass.getDeclaredField("theUnsafe");
            f.setAccessible(true);
            return f.get(null);
        }
    }


    private Object invoke(final Object target, final String methodName, final Class<?>... args) {
        return AccessController.doPrivileged(new PrivilegedAction<Object>() {
            public Object run() {
                try {
                    Method method = method(target, methodName, args);
                    method.setAccessible(true);
                    return method.invoke(target);
                } catch (Exception e) {
                    throw new IllegalStateException(e);
                }
            }
        });
    }

    /**
     * 反射获取target对象中的methodName方法
     * @param target
     * @param methodName
     * @param args
     * @return
     * @throws NoSuchMethodException
     */
    private Method method(Object target, String methodName, Class<?>[] args)
            throws NoSuchMethodException {
        try {
            return target.getClass().getMethod(methodName, args);
        } catch (NoSuchMethodException e) {
            return target.getClass().getDeclaredMethod(methodName, args);
        }
    }

    /**
     * 这个方法用于找到DirectByteBuffer中的att属性, 这个属性即对应真实的用于存储内容的内存
     * @param buffer
     * @return
     */
    private ByteBuffer viewed(ByteBuffer buffer) {
        String methodName = "viewedBuffer";
        Method[] methods = buffer.getClass().getMethods();
        for (int i = 0; i < methods.length; i++) {
            if (methods[i].getName().equals("attachment")) {
                methodName = "attachment";
                break;
            }
        }

        ByteBuffer viewedBuffer = (ByteBuffer) invoke(buffer, methodName);
        if (viewedBuffer == null)
            return buffer;
        else
            return viewed(viewedBuffer);
    }

    public static void main(String[] args) throws IOException, InterruptedException {
        MMapUtil mMapUtil = new MMapUtil();
        //映射1mb
        mMapUtil.loadFileInMMap("D:\\Projects\\eaglemq\\broker\\store\\order_cancel_topic\\00000000", 0, 1024 * 1024 * 1);
        CountDownLatch count = new CountDownLatch(1);
        CountDownLatch allWriteSuccess = new CountDownLatch(10);
        for(int i =0;i<10;i++) {
            int finalI = i;
            Thread task = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        count.await();
                        //多线程并发写
                        mMapUtil.writeContent(("test-content-" + finalI).getBytes());
                        allWriteSuccess.countDown();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            });
            task.start();;
        }
        System.out.println("准备执行并发写入mmap测试");
        count.countDown();
        allWriteSuccess.await();
        System.out.println("并发测试完毕,读取文件内容测试");
        byte[] content = mMapUtil.readContent(0,1000);
        System.out.println("内容："+new String(content));
    }

}
